// Copyright (c) Microsoft Corporation.  All rights reserved.
#include "Common.h"

typedef HRESULT(*PFN_CREATETEXTSERVICES)(IUnknown *punkOuter, ITextHost *pITextHost, IUnknown **ppUnk);

HMODULE g_hmodRiched20;
__encoded_pointer PFN_CREATETEXTSERVICES g_pfnCreateTextServices;

VOID CViewTextItem::Uninitialize()
{
    if (m_pTextDoc)
    {
        m_pTextDoc->Release();
        m_pTextDoc = NULL;
    }

    if (m_pTextSvc)
    {
        m_pTextSvc->Release();
        m_pTextSvc = NULL;
    }

    if (m_pTextHost)
    {
        m_pTextHost->Release();
        m_pTextHost = NULL;
    }
}

HRESULT CViewTextItem::Initialize()
{
    HRESULT hr = S_OK;
    IUnknown *pUnk = NULL;
    CScfTxtElement* pTxtElement = (CScfTxtElement *)m_pScfElement;
    CComObject<CTextHost> *pTextHost;
	
    hr = CComObject<CTextHost>::CreateInstance(&pTextHost);

    // Initializing windowless RichTextEdit control.
    if (SUCCEEDED(hr))
    {
        pTextHost->AddRef();
        pTextHost->SetWordWrap(pTxtElement->m_wrap);
		
        // CTextHost -> ITextHost
        m_pTextHost = pTextHost;

#if 1
        if (!g_hmodRiched20)
        {
            g_hmodRiched20 = LoadLibrary(L"riched20.dll");
        }
        
        if (!g_pfnCreateTextServices && g_hmodRiched20)
        {
            g_pfnCreateTextServices = (PFN_CREATETEXTSERVICES)EncodePointer(GetProcAddress(g_hmodRiched20, "CreateTextServices"));
        }
        
        if (g_pfnCreateTextServices)
        {
            hr = ((PFN_CREATETEXTSERVICES)DecodePointer(g_pfnCreateTextServices))(NULL, m_pTextHost, &pUnk);
        }
        else
        {
            hr = E_FAIL;
        }
#else
//////// there is no riched20.lib for x64
//      hr = ::CreateTextServices(NULL, m_pTextHost, &pUnk);
#endif
    }

    if (SUCCEEDED(hr))
    {
        hr = pUnk->QueryInterface(__uuidof(ITextDocument), (VOID **)&m_pTextDoc);
    }

    if (SUCCEEDED(hr))
    {
        hr = pUnk->QueryInterface(IID_ITextServices, (VOID **)&m_pTextSvc);
    }

    if (SUCCEEDED(hr))
    {
        // set entire text
        hr = m_pTextSvc->TxSetText(pTxtElement->GetText());
    }

    if (SUCCEEDED(hr))
    {
        ITextRange *pTextRange = NULL;
        ITextFont *pTextFont = NULL;
        ITextPara *pTextPara = NULL;

        // Get Entire Text Range
        hr = m_pTextDoc->Range(0, LONG_MAX, &pTextRange);

        if (hr == S_OK)
        {
            hr = pTextRange->GetFont(&pTextFont);

            if (hr == S_OK)
            {
                // Font
                pTextFont->SetName(CComBSTR(CRenderer::GetFontName()));

                pTextFont->SetSize((float)CRenderer::GetFontSizeInPoint());

                pTextFont->SetBold(tomFalse);

                // Text Color
                if (pTxtElement->HasRgbAttr() && CDevice::GetDeviceBitDepth() != 1)
                {
                    pTextFont->SetForeColor(pTxtElement->GetRgbAttr());
                }
                else
                {
                    pTextFont->SetForeColor(CRenderer::GetDefaultTextColor());
                }

                pTextFont->Release();
            }

            // align
            if (pTxtElement->m_align != SCF_ALIGN_LEFT)
            {
                hr = pTextRange->GetPara(&pTextPara);
                if (hr == S_OK)
                {
                    if (pTxtElement->m_align == SCF_ALIGN_CENTER)
                    {
                        pTextPara->SetAlignment(tomAlignCenter);
                    }
                    else if (pTxtElement->m_align == SCF_ALIGN_RIGHT)
                    {
                        pTextPara->SetAlignment(tomAlignRight);
                    }
                    pTextPara->Release();
                }
            }

            pTextRange->Release();
        }

        if (CDevice::GetDeviceBitDepth() == 1)
        {
            // ignore <clr> when 1bit bitmap.
        }
        else
        {
            // apply <clr> element information to RichText
            for(list<CScfTxtClrRange>::iterator it = pTxtElement->m_ClrRanges.begin();
                it != pTxtElement->m_ClrRanges.end();
                ++it)
            {
                ITextRange *pTextRange = NULL;
                ITextFont *pTextFont = NULL;

                hr = m_pTextDoc->Range(it->m_from, it->m_to, &pTextRange);

                if (hr == S_OK)
                {
                    hr = pTextRange->GetFont(&pTextFont);
                    if (hr == S_OK)
                    {
                        pTextFont->SetForeColor(it->m_rgb);
                        pTextFont->Release();
                    }
                    pTextRange->Release();
                }
            }
        }

        // apply <em> element information to RichText
        for(list<CScfTxtEmRange>::iterator it = pTxtElement->m_EmRanges.begin();
            it != pTxtElement->m_EmRanges.end();
            ++it)
        {
            ITextRange *pTextRange = NULL;
            ITextFont *pTextFont = NULL;

            hr = m_pTextDoc->Range(it->m_from, it->m_to, &pTextRange);

            if (hr == S_OK)
            {
                hr = pTextRange->GetFont(&pTextFont);
                if (hr == S_OK)
                {
                    pTextFont->SetBold(tomTrue);
                    pTextFont->Release();
                }
                pTextRange->Release();
            }
        }
    }

    if (pUnk)
    {
        pUnk->Release();
    }

    if (FAILED(hr))
    {
        Uninitialize();
    }

    return hr;
}

HRESULT CViewTextItem::CalcItemHeight(HDC hDC, LONG nClientWidth, LONG nClientHeight)
{
    UNREFERENCED_PARAMETER(nClientHeight);
    
    HRESULT hr = S_OK;
    SIZEL extent = { -1, -1 };

    m_nWidth = nClientWidth - CONTENT_LEFT_MARGIN - CONTENT_RIGHT_MARGIN;
    m_nHeight = 0;

    hr = m_pTextSvc->TxGetNaturalSize(
                DVASPECT_CONTENT,
                hDC,
                NULL,
                NULL,
                TXTNS_FITTOCONTENT,
                &extent,
                &m_nWidth,
                &m_nHeight);

    return hr;
}

HRESULT CViewTextItem::Draw(HDC hDC, LONG left, LONG top, LONG right, LONG bottom, DWORD dwFlags)
{
    HRESULT hr = S_OK;
    RECTL rcl;

    UNREFERENCED_PARAMETER(dwFlags);

    rcl.left = left + CONTENT_LEFT_MARGIN;
    rcl.top = top;
    rcl.right = right - CONTENT_RIGHT_MARGIN;
    rcl.bottom = bottom;

    hr = m_pTextSvc->TxDraw(
                DVASPECT_CONTENT,
                0,
                NULL,
                NULL,
                hDC,
                NULL,
                &rcl,
                NULL,
                NULL,       
                NULL,       // pfnContinue
                0,          // dwConttinue
                0);         // lViewId

//
    g_pDataManager->GetRenderer()->SelectDefaultFont();
//

    return hr;
}

HRESULT CViewImageItem::CalcItemHeight(HDC hDC, LONG nClientWidth, LONG nClientHeight)
{
    UNREFERENCED_PARAMETER(hDC);
    
    HRESULT hr = S_OK;
    CScfImgElement* pImageElement = (CScfImgElement *)m_pScfElement;
    CNodeApplication *pAppNode = g_pDataManager->GetCurrentApplication();

    nClientWidth -= (CONTENT_LEFT_MARGIN + CONTENT_RIGHT_MARGIN);  // margin

    m_nWidth = 0;
    m_nHeight = 0;

    if (!pAppNode)
    {
        return S_OK;
    }

    CContent* pImageContent = pAppNode->GetContent(pImageElement->m_id);

    if (!pImageContent)
    {
        hr = g_pDataManager->GetDDI()->SendContentMissingEvent(pAppNode->m_guidApplicationId, pImageElement->m_id);
    }
    else
    {
        LONG bmWidth = pImageContent->GetImageWidth();
        LONG bmHeight = pImageContent->GetImageHeight();

        switch(pImageElement->m_fit)
        {
            case SCF_IMAGEFIT_AUTO:
            case SCF_IMAGEFIT_NATIVE:
                m_nWidth = bmWidth;
                m_nHeight = bmHeight;
                break;

            case SCF_IMAGEFIT_WIDTH:
                m_nWidth = nClientWidth;
                m_nHeight = bmHeight * nClientWidth / bmWidth;
                break;

            case SCF_IMAGEFIT_SCREEN:
                m_nWidth = nClientWidth;
                m_nHeight = bmHeight * nClientWidth / bmWidth;

                if (m_nHeight > nClientHeight)
                {
                    m_nWidth = m_nWidth * nClientHeight / m_nHeight;
                    m_nHeight = nClientHeight;
                } 
                break;
        }
    }

    return hr;
}

HRESULT CViewImageItem::Draw(HDC hDC, LONG left, LONG top, LONG right, LONG bottom, DWORD dwFlags)
{
    UNREFERENCED_PARAMETER(bottom);
    UNREFERENCED_PARAMETER(dwFlags);

    CScfImgElement* pImageElement = (CScfImgElement *)m_pScfElement;
    CNodeApplication *pAppNode = g_pDataManager->GetCurrentApplication();

    if (!pAppNode)
    {
        return S_OK;
    }

    CContent* pImageContent = pAppNode->GetContent(pImageElement->m_id);

    if (pImageContent)
    {
        left += CONTENT_LEFT_MARGIN;
        right -= CONTENT_RIGHT_MARGIN;

//      if (m_nWidth < (right - left))
//      {
            switch(pImageElement->m_align)
            {
                case SCF_ALIGN_CENTER:
                    left = left + ((right - left) - m_nWidth) / 2;
                    break;
                case SCF_ALIGN_RIGHT:
                    left = right - m_nWidth;
                    break;
            }
//      }

        pImageContent->DrawImage(
                            hDC,
                            left,
                            top,
                            m_nWidth,
                            m_nHeight);
    }

    return S_OK;
}

/*
    MSDN:
    Menu items can be rendered on at most two lines, depending on whether they specify an explicit line
    break (the br element) in their text. Items that specify a line break should always be rendered on
    two lines, with the content on the second line being the text immediately following the br element.
    Items without an explicit break should be rendered on only one line. If text is too long to fit onto
    a single line, it should be truncated. When a single-line menu item is selected, however, it should
    wrap onto two lines to allow maximum visibility for any truncated text.
*/
/*
    +---------------------------------------------------------------------+
    |                                                                     |
    |<-------------------------- ClientWidth ---------------------------->|
    |<-><--------------><-><------------------------------------------><->|
    | |     ImageWidth   6             TextAreaWidth                    | |
      |                                                                 |
     LeftMargin                                                RightMargin
*/
HRESULT CViewMenuItem::CalcItemHeight(HDC hDC, LONG nClientWidth, LONG nClientHeight)
{
    UNREFERENCED_PARAMETER(nClientHeight);
    UNREFERENCED_PARAMETER(hDC);
    
    m_nWidth = nClientWidth;
    m_nHeight = 0;
    m_nImageWidth = 0;
    m_nImageHeight = 0;
    m_bLongSingleLine = FALSE;
    
    if (m_pScfElement->GetElementType() == SCF_ITEM_ELEMENT)
    {
        CScfItemElement *pItem = (CScfItemElement *)m_pScfElement;

        //
        // if the item has img, get the image size, or raise content missing
        //
        if (pItem->m_imgId)
        {
            CNodeApplication *pAppNode = g_pDataManager->GetCurrentApplication();

            CContent *pImgContent = pAppNode->GetContent(pItem->m_imgId);

            if (pImgContent)
            {
                m_nImageHeight = pImgContent->GetImageHeight();
                m_nImageWidth = pImgContent->GetImageWidth();
            }
            else
            {
                g_pDataManager->GetDDI()->SendContentMissingEvent(
                                    pAppNode->m_guidApplicationId,
                                    pItem->m_imgId);
            }
        }

        //
        // does this menu item has <br> element?
        //
        const WCHAR *pBr = wcschr(pItem->GetText(), L'\n');
        
        if (pBr)
        {
            //
            // 2 line
            //
            m_nHeight = CRenderer::GetMenuItemHeight() * 2;
        }
        else
        {
            //
            // 1 line
            //
            m_nHeight = min((LONG)CRenderer::GetMenuItemHeight() * 2, max((LONG)CRenderer::GetMenuItemHeight(), (m_nImageHeight + 1)));
        }
        
        //
        // adjust image size
        //
        if (m_nImageHeight >= m_nHeight)
        {
            m_nImageWidth =  m_nImageWidth * m_nHeight / m_nImageHeight;
            m_nImageHeight = m_nHeight;
        }

        //
        // Y margin
        //
#ifndef _THICK_FRAME_CONTEXT_MENU
        if (GetParentView()->GetViewType() == SCF_MENU_VIEW)
        {
#endif
            m_nHeight += SELECTED_MENU_ITEM_FRAME_HEIGHT * 2;
#ifndef _THICK_FRAME_CONTEXT_MENU
        }
#endif
        
        //
        // special handling for long single line text
        //
        if (!pBr)
        {
            if (GetItemExtent(hDC) > nClientWidth)
            {
                m_bLongSingleLine = TRUE;
            }
        }
    }
    else if (m_pScfElement->GetElementType() == SCF_DIV_ELEMENT)
    {
        m_nHeight = 2;
    }

    return S_OK;
}

//
// This is for LongSingleLineItem
//
LONG CViewMenuItem::GetSelectedItemHeight()
{
    LONG ret = CRenderer::GetMenuItemHeight() * 2;
    
    //
#ifndef _THICK_FRAME_CONTEXT_MENU
    if (GetParentView()->GetViewType() == SCF_MENU_VIEW)
    {
#endif
        ret += SELECTED_MENU_ITEM_FRAME_HEIGHT * 2;
#ifndef _THICK_FRAME_CONTEXT_MENU
    }
#endif
    //
    
    return ret;
}

//
// This function returns actual item width.
// This is used by check for LongSingleLine and scaling context menu width.
// This should be called after OnSize.
//
LONG CViewMenuItem::GetItemExtent(HDC hDC)
{
    CScfItemElement *pItem = (CScfItemElement *)m_pScfElement;
    LONG ret = 0;

    ret = CONTENT_LEFT_MARGIN;

    if (m_nImageWidth)
    {
        ret += m_nImageWidth + 6;
    }

    SIZE size = { 0 };

    if (GetTextExtentPoint32(hDC, pItem->GetText(), pItem->GetTextLength(), &size))
    {
        ret += size.cx;
    }
    
    ret += CONTENT_RIGHT_MARGIN;
    
    return ret;
}

//
// draw menu item
//
HRESULT CViewMenuItem::Draw(HDC hDC, LONG left, LONG top, LONG right, LONG bottom, DWORD dwFlags)
{
    UNREFERENCED_PARAMETER(bottom);

    if (m_pScfElement->GetElementType() == SCF_ITEM_ELEMENT)
    {
        CScfItemElement *pItem = (CScfItemElement *)m_pScfElement;

        RECT rect = { left, top, right, bottom };

        //
        // draw menu item background
        //
        if (GetParentView()->GetViewType() == SCF_MENU_VIEW)
        {
            g_pDataManager->GetRenderer()->DrawMenuItemBG(
                    rect,
                    (dwFlags & VIEW_ITEM_FLAG_SELECTED) != 0);      // bSelected
        }
        else
        {
            g_pDataManager->GetRenderer()->DrawContextMenuItemBG(
                    rect,
                    (dwFlags & VIEW_ITEM_FLAG_SELECTED) != 0);      // bSelected
        }
        
        // apply left/right margin
        rect.left += CONTENT_LEFT_MARGIN;
        rect.right -= CONTENT_RIGHT_MARGIN;

#ifndef _THICK_FRAME_CONTEXT_MENU
        if (GetParentView()->GetViewType() == SCF_MENU_VIEW)
        {
#endif
            rect.top += SELECTED_MENU_ITEM_FRAME_HEIGHT;
            rect.bottom -= SELECTED_MENU_ITEM_FRAME_HEIGHT;
#ifndef _THICK_FRAME_CONTEXT_MENU
        }
#endif
        //

        //
        // draw menu item image
        //
        if (m_nImageWidth)
        {
            CNodeApplication *pAppNode = g_pDataManager->GetCurrentApplication();

            CContent *pImgContent = pAppNode->GetContent(pItem->m_imgId);

            if (pImgContent)
            {
                pImgContent->DrawImage(
                            hDC,
                            rect.left,
                            rect.top + (((rect.bottom - rect.top) - m_nImageHeight) / 2),
                            m_nImageWidth,
                            m_nImageHeight);
            }

            rect.left += m_nImageWidth + 6;
        }

        //
        // draw menu item text
        //
        const WCHAR *pBr = wcschr(pItem->GetText(), L'\n');
        UINT dwDtFormat = DT_LEFT | DT_NOPREFIX | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS;
        
        if ((dwFlags & VIEW_ITEM_FLAG_SELECTED) != 0)
        {
            SetTextColor(hDC, CRenderer::GetMenuActiveItemTextColor());
        }

        if (pBr)
        {
            // 2 line text

            rect.top++;

            DrawText(hDC, pItem->GetText(), (int)(pBr - pItem->GetText()), &rect, dwDtFormat | DT_SINGLELINE);

            rect.top += CRenderer::GetMenuItemHeight();

            DrawText(hDC, pBr + 1, -1, &rect, dwDtFormat | DT_SINGLELINE);
        }
        else
        {
            // 1 line text
            if (m_bLongSingleLine && (dwFlags & VIEW_ITEM_FLAG_SELECTED) != 0)
            {
                //
                // When long 1 line text is selected, it should be wrapped in 2 lines.
                //
                // Single DrawText call with DT_WORDWRAP and DT_XXXELLIPSIS didn't work.
                // It didn't put an ellipsis at end of the 2nd line.
                //
                DRAWTEXTPARAMS dtParams;
                
                ZeroMemory(&dtParams, sizeof(dtParams));
                dtParams.cbSize = sizeof(dtParams);

                // get 1st line length.
                // if the rect has enough height, DrawTextEx returns uiLengthDrawn including number of characters of 2nd line. 
                // that's why I specifed half of height here.
                rect.bottom = rect.top + CRenderer::GetMenuItemHeight() / 2;    // /2 is a hack to get correct uiLengthDrawn.
                DrawTextEx(hDC, (LPWSTR)pItem->GetText(), -1, &rect, DT_LEFT | DT_NOPREFIX | DT_WORDBREAK, &dtParams);

                // draw 1st line (again)
                rect.bottom = rect.top + CRenderer::GetMenuItemHeight() ;
                DrawText(hDC, (LPWSTR)pItem->GetText(), dtParams.uiLengthDrawn, &rect, DT_LEFT | DT_NOPREFIX | DT_WORDBREAK);
                
                // draw 2nd line
                rect.top += CRenderer::GetMenuItemHeight();
                rect.bottom = rect.top + CRenderer::GetMenuItemHeight();
                DrawText(hDC, pItem->GetText() + dtParams.uiLengthDrawn, -1, &rect, dwDtFormat);
            }
            else
            {
                DrawText(hDC, pItem->GetText(), -1, &rect, dwDtFormat | DT_SINGLELINE | DT_VCENTER);
            }

        }

        if ((dwFlags & VIEW_ITEM_FLAG_SELECTED) != 0)
        {
            SetTextColor(hDC, CRenderer::GetDefaultTextColor());
        }
    }
    else if (m_pScfElement->GetElementType() == SCF_DIV_ELEMENT)
    {
        RECT rect = { left, top, right, bottom };
        
        g_pDataManager->GetRenderer()->DrawMenuDivLine(
                hDC,
                rect,
                GetParentView()->GetViewType() == SCF_CONTEXT_MENU_VIEW);
    }

    return S_OK;
}

